const wu=require("./wuLib.js");
const {VM}=require('vm2');
function catchZGroup(code,groupPreStr,cb){
	const debugPre="(function(z){var a=11;function Z(ops,debugLine){";
	let zArr={};
	for(let preStr of groupPreStr){
		let content=code.slice(code.indexOf(preStr)),z=[];
		content=content.slice(content.indexOf("(function(z){var a=11;"));
		content=content.slice(0,content.indexOf("})(__WXML_GLOBAL__.ops_cached.$gwx"))+"})(z);";
		let vm=new VM({sandbox:{z:z,debugInfo:[]}});
		vm.run(content);
		if(content.startsWith(debugPre))for(let i=0;i<z.length;i++)z[i]=z[i][1];
		zArr[preStr.match(/function gz\$gwx(\d*\_\d+)/)[1]]=z;
	}
	cb({"mul":zArr});
}
function catchZ(code,cb){
	let groupTest=code.match(/function gz\$gwx(\d*\_\d+)\(\)\{\s*if\( __WXML_GLOBAL__\.ops_cached\.\$gwx\d*\_\d+\)/g);
	if(groupTest!==null)return catchZGroup(code,groupTest,cb);
	let z=[],vm=new VM({sandbox:{
		z:z,
		debugInfo:[]
	}});
	let lastPtr=code.lastIndexOf("(z);__WXML_GLOBAL__.ops_set.$gwx=z;");
	if(lastPtr==-1)lastPtr=code.lastIndexOf("(z);__WXML_GLOBAL__.ops_set.$gwx");
	code=code.slice(code.indexOf('(function(z){var a=11;function Z(ops){z.push(ops)}'),lastPtr+4);
	vm.run(code);
	cb(z);
}
function restoreSingle(ops,withScope=false){
	if(typeof ops=="undefined")return "";
	function scope(value){
		if(value.startsWith('{')&&value.endsWith('}'))return withScope?value:"{"+value+"}";
		return withScope?value:"{{"+value+"}}";
	}
	function enBrace(value,type='{'){
		if(value.startsWith('{')||value.startsWith('[')||value.startsWith('(')||value.endsWith('}')||value.endsWith(']')||value.endsWith(')'))value=' '+value+' ';
		switch(type){
			case '{':
			return '{'+value+'}';
			case '[':
			return '['+value+']';
			case '(':
			return '('+value+')';
			default:
			throw Error("Unknown brace type "+type);
		}
	}
	function restoreNext(ops,w=withScope){
		return restoreSingle(ops,w);
	}
	function jsoToWxon(obj){//convert JS Object to Wechat Object Notation(No quotes@key+str)
		let ans="";
		if(typeof obj==="undefined"){
			return 'undefined';
		}else if(obj===null){
			return 'null';
		}else if(obj instanceof RegExp){
			return obj.toString();
		}else if(obj instanceof Array){
			for(let i=0;i<obj.length;i++)ans+=','+jsoToWxon(obj[i]);
			return enBrace(ans.slice(1),'[');
		}else if(typeof obj=="object"){
			for(let k in obj)ans+=","+k+":"+jsoToWxon(obj[k]);
			return enBrace(ans.slice(1),'{');
		}else if(typeof obj=="string"){
			let parts=obj.split('"'),ret=[];
			for(let part of parts){
				let atoms=part.split("'"),ans=[];
				for(let atom of atoms)ans.push(JSON.stringify(atom).slice(1,-1));
				ret.push(ans.join("\\'"));
			}
			return "'"+ret.join('"')+"'";
		}else return JSON.stringify(obj);
	}
	let op=ops[0];
	if(typeof op!="object"){
		switch(op){
		case 3://string
			return ops[1];//may cause problems if wx use it to be string
		case 1://direct value
			return scope(jsoToWxon(ops[1]));
		case 11://values list, According to var a = 11;
			let ans="";
			ops.shift();
			for(let perOp of ops)ans+=restoreNext(perOp);
			return ans;
		}
	}else{
		let ans="";
		switch(op[0]){//vop
		case 2://arithmetic operator
		{
			function getPrior(op,len){
				const priorList={
					"?:":4,"&&":6,"||":5,"+":13,"*":14,"/":14,"%":14,"|":7,"^":8,"&":9,"!":16,"~":16,
					"===":10,"==":10,"!=":10,"!==":10,">=":11,"<=":11,">":11,"<":11,"<<":12,">>":12,
					"-":len==3?13:16
				};
				return priorList[op]?priorList[op]:0;
			}
			function getOp(i){
				let ret=restoreNext(ops[i],true);
				if(ops[i] instanceof Object&&typeof ops[i][0]=="object"&&ops[i][0][0]==2){
					//Add brackets if we need
					if(getPrior(op[1],ops.length)>getPrior(ops[i][0][1],ops[i].length))ret=enBrace(ret,'(');;
				}
				return ret;
			}
			switch(op[1]){
			case"?:":
				ans=getOp(1)+"?"+getOp(2)+":"+getOp(3);
				break;
			case "!":
			case "~":
				ans=op[1]+getOp(1);
				break;
			case"-":
				if(ops.length!=3){
					ans=op[1]+getOp(1);
					break;
				}//shoud not add more in there![fall through]
			default:
				ans=getOp(1)+op[1]+getOp(2);
			}
			break;
		}
		case 4://unkown-arrayStart?
			ans=restoreNext(ops[1],true);
			break;
		case 5://merge-array
		{
			switch (ops.length) {
			case 2:
				ans=enBrace(restoreNext(ops[1],true),'[');
				break;
			case 1:
				ans='[]';
				break;
			default:
			{
				let a=restoreNext(ops[1],true);
				//console.log(a,a.startsWith('[')&&a.endsWith(']'));
				if(a.startsWith('[')&&a.endsWith(']')){
					if(a!='[]'){
						ans=enBrace(a.slice(1,-1).trim()+','+restoreNext(ops[2],true),'[');
						//console.log('-',a);
					}else{
						ans=enBrace(restoreNext(ops[2],true),'[');
					}
				}else{
					ans=enBrace('...'+a+','+restoreNext(ops[2],true),'[');//may/must not support in fact
				}
			}
			}
			break;
		}
		case 6://get value of an object
		{
			let sonName=restoreNext(ops[2],true);
			if(sonName._type==="var")
				ans=restoreNext(ops[1],true)+enBrace(sonName,'[');
			else{
				let attach="";
				if(/^[A-Za-z\_][A-Za-z\d\_]*$/.test(sonName)/*is a qualified id*/)
					attach='.'+sonName;
				else attach=enBrace(sonName,'[');
				ans=restoreNext(ops[1],true)+attach;
			}
			break;
		}
		case 7://get value of str
		{
			switch(ops[1][0]){
			case 11:
				ans=enBrace("__unTestedGetValue:"+enBrace(jsoToWxon(ops),'['),'{');
				break;
			case 3:
				ans=new String(ops[1][1]);
				ans._type="var";
				break;
			default:
				throw Error("Unknown type to get value");
			}
			break;
		}
		case 8://first object
			ans=enBrace(ops[1]+':'+restoreNext(ops[2],true),'{');//ops[1] have only this way to define
			break;
		case 9://object
		{
			function type(x){
				if(x.startsWith('...'))return 1;
				if(x.startsWith('{')&&x.endsWith('}'))return 0;
				return 2;
			}
			let a=restoreNext(ops[1],true);
			let b=restoreNext(ops[2],true);
			let xa=type(a),xb=type(b);
			if(xa==2||xb==2)ans=enBrace("__unkownMerge:"+enBrace(a+","+b,'['),'{');
			else{
				if(!xa)a=a.slice(1,-1).trim();
				if(!xb)b=b.slice(1,-1).trim();
				//console.log(l,r);
				ans=enBrace(a+','+b,'{');
			}
			break;
		}
		case 10://...object
			ans='...'+restoreNext(ops[1],true);
			break;
		case 12:
		{
			let arr=restoreNext(ops[2],true);
			if(arr.startsWith('[')&&arr.endsWith(']'))
				ans=restoreNext(ops[1],true)+enBrace(arr.slice(1,-1).trim(),'(');
			else ans=restoreNext(ops[1],true)+'.apply'+enBrace('null,'+arr,'(');
			break;
		}
		default:
			ans=enBrace("__unkownSpecific:"+jsoToWxon(ops),'{');
		}
		return scope(ans);
	}
}
function restoreGroup(z){
	let ans=[];
	for(let g in z.mul){
		let singleAns=[];
		for(let e of z.mul[g])singleAns.push(restoreSingle(e,false));
		ans[g]=singleAns;
	}
	let ret=[];//Keep a null array for remaining global Z array.
	ret.mul=ans;
	return ret;
}
function restoreAll(z){
	if(z.mul)return restoreGroup(z);
	let ans=[];
	for(let e of z)ans.push(restoreSingle(e,false));
	return ans;
}
module.exports={getZ(code,cb){
	catchZ(code,z=>cb(restoreAll(z)));
}};
